Since 6.11, we allow multiple types to be active for the same URL.
While we converted multiple places to handle this, we missed adjusting
the property validator. There, we just picked the last inserted property
cache, which can lead to validation failures.
Fix this by adding a fallback path which checks all potential matches
when the initial lookup would yield an error.
The same kind of check is also needed in qmlobject_can_qml_cast.
Amends
9cc23ca91c404f24fff3c36e8c9425ae674f341d.
Change compared to 6.12:
Dropped the isComposite check, which doesn't exist there. That causes
one potential extra unneeded lock acquisition, but only in a case
where'd we get an error anyway.
Fixes: QTBUG-149607
Change-Id: Ibd0df2325807e75b23056ebc0bacdfe877b002f3
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
(cherry picked from commit
94a76ed58629932f6d689c48ecf8d3132188923b)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit
8c23f65ec4889f9ff7f609002a129698796de2b8)
Gbp-Pq: Name upstream_2efb7c6_correctly_compare_composites_with_multiple_engines.diff
// A non-composite type will always have a metaobject.
const QMetaObject *typeMetaObject = type.metaObject();
- const QQmlPropertyCache::ConstPtr typePropertyCache = typeMetaObject
- ? QQmlPropertyCache::ConstPtr()
- : QQmlMetaType::findPropertyCacheInCompositeTypes(type.typeId());
+ QVarLengthArray<QQmlPropertyCache::ConstPtr, 4> allPropertyCacheCandidates =
+ typeMetaObject ? QVarLengthArray<QQmlPropertyCache::ConstPtr, 4>{}
+ : QQmlMetaType::rawCompositePropertyCachesForType(type.typeId());
if (const QQmlData *ddata = ddata_for_cast(object)) {
for (const QQmlPropertyCache *propertyCache = ddata->propertyCache.data(); propertyCache;
// property caches to be unrelated but the types still convertible.
// Multiple property caches can hold the same metaobject, for example for
// versions of non-composite types.
- if (propertyCache == typePropertyCache.data())
+ if (allPropertyCacheCandidates.contains(propertyCache))
return true;
}
}
}
- // If nothing else works, we have to create the metaobjects.
+ // If nothing else works, we have to create the metaobjects (if we can).
+ if (!typeMetaObject && !allPropertyCacheCandidates.isEmpty())
+ typeMetaObject = allPropertyCacheCandidates.first()->createMetaObject();
- return object->metaObject()->inherits(typeMetaObject
- ? typeMetaObject
- : (typePropertyCache ? typePropertyCache->createMetaObject() : nullptr));
+ return object->metaObject()->inherits(typeMetaObject);
}
QT_END_NAMESPACE
: QQmlPropertyCache::ConstPtr();
}
+/*!
+ * \internal
+ *
+ * Returns all candidate property caches for a composite
+ * metatype instead of only the last inserted one.
+ * compare rawPropertyCacheForType (which handles however also non-composites)
+ */
+QVarLengthArray<QQmlPropertyCache::ConstPtr, 4>
+QQmlMetaType::rawCompositePropertyCachesForType(QMetaType metaType)
+{
+ const QQmlMetaTypeDataPtr data;
+ return data->findPropertyCachesInCompositeTypes(metaType);
+}
+
/*!
* \internal
*
#include <private/qtqmlglobal_p.h>
#include <QtCore/qtyperevision.h>
+#include <QtCore/qvarlengtharray.h>
QT_BEGIN_NAMESPACE
static QQmlPropertyCache::ConstPtr rawPropertyCacheForType(
QMetaType metaType, QTypeRevision version);
+ // All property caches for a composite metatype, which may map to more than one of them.
+ static QVarLengthArray<QQmlPropertyCache::ConstPtr, 4> rawCompositePropertyCachesForType(
+ QMetaType metaType);
+
static bool canConvert(QObject *o, QMetaType metaType);
static bool canConvert(const QQmlPropertyCache::ConstPtr &from, QMetaType metaType);
: propertyCacheForPotentialInlineComponentType(t, iter);
}
+QVarLengthArray<QQmlPropertyCache::ConstPtr, 4>
+QQmlMetaTypeData::findPropertyCachesInCompositeTypes(QMetaType t) const
+{
+ QVarLengthArray<QQmlPropertyCache::ConstPtr, 4> result;
+ const auto [begin, end] = compositeTypes.equal_range(t.iface());
+ for (auto iter = begin; iter != end; ++iter) {
+ if (auto cache = propertyCacheForPotentialInlineComponentType(t, iter))
+ result.append(std::move(cache));
+ }
+ return result;
+}
+
void QQmlMetaTypeData::clearCompositeTypes()
{
// Unregister all remaining composite types.
#include <private/qqmlvaluetype_p.h>
#include <QtCore/qset.h>
+#include <QtCore/qvarlengtharray.h>
#include <QtCore/qvector.h>
QT_BEGIN_NAMESPACE
QQmlPropertyCache::ConstPtr propertyCache(const QQmlType &type, QTypeRevision version);
QQmlPropertyCache::ConstPtr findPropertyCacheInCompositeTypes(QMetaType t) const;
+ // Same, but returns all matches rather than only the last inserted one.
+ QVarLengthArray<QQmlPropertyCache::ConstPtr, 4> findPropertyCachesInCompositeTypes(
+ QMetaType t) const;
+
static QQmlPropertyCache::ConstPtr propertyCacheForPotentialInlineComponentType(
QMetaType t, const QQmlMetaTypeData::CompositeTypes::const_iterator &iter);
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant
+#include "qqmlmetatype_p.h"
#include "qqmlpropertyvalidator_p.h"
#include <private/qqmlcustomparser_p.h>
// Will be true if the assigned type inherits propertyMetaObject
// Determine isAssignable value
bool isAssignable = false;
- QQmlPropertyCache::ConstPtr c = propertyCaches.at(binding->value.objectIndex);
- while (c && !isAssignable) {
- isAssignable |= c == propertyMetaObject;
- c = c->parent();
+ QQmlPropertyCache::ConstPtr source = propertyCaches.at(binding->value.objectIndex);
+
+ const auto inheritsFrom = [&](const QQmlPropertyCache::ConstPtr &target) {
+ for (QQmlPropertyCache::ConstPtr c = source; c; c = c->parent()) {
+ if (c == target)
+ return true;
+ }
+ return false;
+ };
+
+ isAssignable = inheritsFrom(propertyMetaObject);
+
+ if (!isAssignable) {
+ // A single (composite) metatype can map to multiple property caches when
+ // there are multiple engines; rawPropertyCacheForType only returns one of
+ // them. For non-composite types this yields an empty list and is a no-op.
+ const auto candidates = QQmlMetaType::rawCompositePropertyCachesForType(propType);
+ for (const auto &candidate : candidates) {
+ if (inheritsFrom(candidate)) {
+ isAssignable = true;
+ break;
+ }
+ }
}
if (!isAssignable) {
--- /dev/null
+import QtQml
+
+QtObject {}
--- /dev/null
+import QtQml
+
+QtObject {
+ property Foo theFoo: Foo {}
+
+ function check(foo: Foo) : bool {
+ return foo !== null;
+ }
+}
--- /dev/null
+import QtQml
+
+QtObject {
+ property Foo f: Foo {}
+}
--- /dev/null
+import QtQml
+
+QtObject {
+ property Foo f: Foo {}
+}
--- /dev/null
+import QtQml
+
+QtObject {}
void instanceof();
void instanceofMultiEngine();
void instanceofMultiEngineInlineComponent();
+ void propertyValidatorMultiEngine();
+ void canQmlCastMultiEngine();
void concurrentLoadQmlDir();
QVERIFY(result2.toBool());
}
+// verify that the property validator does not get confused when
+// multiple engines end up registering the same type, leading to
+// multiple entries in compositeTypes
+void tst_qqmllanguage::propertyValidatorMultiEngine()
+{
+ QQmlEngine engine1;
+ QQmlComponent c1(&engine1, testFileUrl("propertyValidatorMultiEngine/Consumer1.qml"));
+ QVERIFY2(c1.isReady(), qPrintable(c1.errorString()));
+
+ QQmlEngine engine2;
+ QQmlComponent c2(&engine2, testFileUrl("propertyValidatorMultiEngine/Consumer1.qml"));
+ QVERIFY2(c2.isReady(), qPrintable(c2.errorString()));
+
+ QQmlComponent c3(&engine1, testFileUrl("propertyValidatorMultiEngine/Consumer2.qml"));
+ QVERIFY2(c3.isReady(), qPrintable(c3.errorString()));
+}
+
+// Verify that qmlobject_can_qml_cast() still recognizes a composite type when
+// multiple engines have loaded the same QML file
+void tst_qqmllanguage::canQmlCastMultiEngine()
+{
+ const QUrl url = testFileUrl("canQmlCastMultiEngine/Main.qml");
+
+ QQmlEngine engine1;
+ QQmlComponent c1(&engine1, url);
+ QVERIFY2(c1.isReady(), qPrintable(c1.errorString()));
+ QScopedPointer<QObject> obj1(c1.create());
+ QVERIFY(obj1);
+
+ // A second engine loads the same file and registers another compilation unit
+ // (and thus another property cache) for Foo.
+ QQmlEngine engine2;
+ QQmlComponent c2(&engine2, url);
+ QVERIFY2(c2.isReady(), qPrintable(c2.errorString()));
+ QScopedPointer<QObject> obj2(c2.create());
+ QVERIFY(obj2);
+
+ // Call the typed function on engine1's object with engine1's Foo instance.
+ QQmlExpression expr(engine1.contextForObject(obj1.data()), obj1.data(),
+ QStringLiteral("check(theFoo)"));
+ const QVariant result = expr.evaluate();
+ QVERIFY2(!expr.hasError(), qPrintable(expr.error().description()));
+ QVERIFY(result.toBool());
+}
+
void tst_qqmllanguage::concurrentLoadQmlDir()
{
ThreadedTestHTTPServer server(dataDirectory());